﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Specialized;
using System.IO;
using System.Collections.Generic;
using System.Collections;

namespace WikiDoc.Formatter
{
    /// <summary>
    /// 格式化为 XmlDoc 标准文本。
    /// </summary>
    /// <remarks>
    /// 根据 MS 建议的 XML 注释标记所表明的，XmlDoc 标准标记和 Html 标记并不完全相同：
    ///     * 段落标记是 para 而不是 p
    ///     * 列表以及表格标记使用 list type="bullet" | "number" | "table" 而不是 ul, ol, table
    ///     * 代码段使用 code 标记，而不是 pre 标记
    /// 
    /// 换行规则：
    ///  * 除特别指出的以外，一般结束标签后都不换行
    ///  * 这些结束标签前换行
    ///     * code
    ///  * UL OL 开始标签前换行，如果是嵌套的列表
    ///  * 这些开始标签前换行
    ///     * para
    ///     * table
    ///     * ul
    ///     * ol
    ///     * code
    ///     * pre
    ///  * 这些开始标签后换行
    ///     * code
    ///     * table
    ///     * tr
    ///     * ul
    ///     * ol
    ///  * 这些开始标签后增加缩进
    ///     * ul
    ///     * ol
    ///     * table
    ///     * tr
    ///  * 这些结束标签前减少缩进
    ///     * ul
    ///     * ol
    ///     * table
    ///     * tr
    /// </remarks>
    public class XmlDocFormatter : IFormatter
    {
        /// <summary>
        /// 创建 <see cref="XmlDocFormatter"/> class 新实例
        /// </summary>
        public XmlDocFormatter()
        {
            Writer = new StringWriter();
            IndentSize = 4;
        }

        /// <summary>
        /// 
        /// </summary>
        public TextWriter Writer { get; set; }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return Writer.ToString();
        }

        private bool _started;

        // 写入器当前的状态
        private WriteState _state = WriteState.Start;

        /// <summary>
        /// 当前还未完成的标签，一旦标签的开始标志关闭，本字段就被置为空值
        /// </summary>
        private string _currentTag;

        private bool _appendIndent = true;
        private int _listLevel;

        public void Reset()
        {
            _started = false;
            _currentTag = null;
            _state = WriteState.Start;
            _appendIndent = true;
            _listLevel = 0;
        }

        /// <summary>
        /// 写入器当前的状态
        /// </summary>
        /// <value>The state.</value>
        protected WriteState State
        {
            get { return _state; }
            set { _state = value; }
        }

        private readonly HashSet<string> _newLineTags 
            = new HashSet<string>{"p", "para", "table", "code", "pre", "ol", "ul", "tr", "li", "hr", "h", "h2", "h3", "h4"};
 
        /// <summary>
        /// Begins the tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        public virtual void BeginTag(string tag)
        {
            if (tag == null) throw new ArgumentNullException("tag");

            CloseBeginTag();

            // 在这些标签前换行
            if (_started && _newLineTags.Contains(tag))
            {
                WriteLine();
            }

            switch (tag)
            {
                case "ol":
                case "ul":
                    _listLevel++;
                    break;
                case "br":
                    return;
            }

            Write('<');
            Write(tag);

            _currentTag = tag;
            _state = WriteState.Attribute;
        }

        /// <summary>
        /// 如果当前的开始标签还未关闭，则关闭它，否则什么也不做
        /// </summary>
        private void CloseBeginTag()
        {
            if (_currentTag == null)
            {
                return;
            }

            Write('>');

            // 在这些标签后换行
            switch (_currentTag)
            {
                case "code":
                case "pre":
                    WriteLine();
                    break;
            }

            // 在这些标签后缩进
            switch (_currentTag)
            {
                case "table":
                case "ul":
                case "ol":
                    Indent();
                    break;
            }

            _currentTag = null;
            _state = WriteState.BeforeContent;
        }

        /// <summary>
        /// Adds the attribute.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public virtual void AddAttribute(string name, string value)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (value == null) throw new ArgumentNullException("value");

            System.Diagnostics.Debug.Assert(_state == WriteState.Attribute);

            // value 中的双引号要被替换为连续的双引号
            Write(string.Format(" {0}=\"{1}\"", name, value.Replace("\"", "\"\"")));
        }

        /// <summary>
        /// Appends the content.
        /// </summary>
        /// <param name="str">The STR.</param>
        public void AppendContent(string str)
        {
            if (str == null) throw new ArgumentNullException("str");

            System.Diagnostics.Debug.Assert(_state != WriteState.Start);

            CloseBeginTag();
            
            if (_state == WriteState.BeforeContent)
            {
                Write(str);
            }
            else
            {
                WriteLine();

                if (_listLevel > 0)
                { // 列表的续行缩进
                    Indent();
                    Write(str);
                    DeIndent();
                }
                else
                {
                    Write(str);
                }
            }

            _state = WriteState.Content;
        }

        /// <summary>
        /// Ends the tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        public virtual void EndTag(string tag)
        {
            if (tag == null) throw new ArgumentNullException("tag");

            if (tag == "br") return;

            if (_state == WriteState.Attribute)
            {
                Write("/>");
            }

            switch (tag)
            {
                case "ol":
                case "ul":
                case "table":
                    WriteLine();
                    DeIndent();
                    break;
                case "code":
                case "pre":
                    WriteLine();
                    break;
            }

            if (_state != WriteState.Attribute)
            {
                Write("</");
                Write(tag);
                Write('>');
            }

            switch (tag)
            {
                case "ol":
                case "ul":
                    _listLevel--;
                    break;
            }

            _currentTag = null;
            _state = WriteState.Start;
        }

        /// <summary>
        /// Gets or sets the size of the indent.
        /// </summary>
        /// <value>The size of the indent.</value>
        public int IndentSize { get; set;}

        /// <summary>
        /// 当前列表嵌套级别
        /// </summary>
        /// <value>The list level.</value>
        public int ListLevel
        {
            get { return _listLevel; }
        }

        private int _indentLevel;

        /// <summary>
        /// Indents this instance.
        /// </summary>
        public void Indent()
        {
            _indentLevel ++;
        }

        /// <summary>
        /// Unindents this instance.
        /// </summary>
        public void DeIndent()
        {
            _indentLevel --;

            if (_indentLevel < 0) _indentLevel = 0;
        }

        /// <summary>
        /// Writes the indent.
        /// </summary>
        private void WriteIndent()
        {
            if (_appendIndent)
            {
                _appendIndent = false;
                Writer.Write(new string(' ', IndentSize * _indentLevel));
                _started = true;
            }
        }

        /// <summary>
        /// Writes the specified ch.
        /// </summary>
        /// <param name="ch">The ch.</param>
        private void Write(char ch)
        {
            WriteIndent();
            Writer.Write(ch);
            _started = true;
        }

        /// <summary>
        /// Writes the specified string.
        /// </summary>
        /// <param name="s">The s.</param>
        private void Write(string s)
        {
            if (s.Length == 0) return;

            WriteIndent();
            Writer.Write(s);
            _started = true;
        }

        /// <summary>
        /// Writes the space line.
        /// </summary>
        protected void WriteLine()
        {
            Writer.WriteLine();
            _appendIndent = true;
            _started = true;
        }
    }
}
